/* *
 * --ライセンスについて--
 *
 * 「本ファイルの内容は Mozilla Public License Version 1.1 (「本ライセンス」)
 * の適用を受けます。
 * 本ライセンスに従わない限り本ファイルを使用することはできません。
 * 本ライセンスのコピーは http://www.mozilla.org/MPL/ から入手できます。
 *
 * 本ライセンスに基づき配布されるソフトウェアは、「現状のまま」で配布されるものであり、
 * 明示的か黙示的かを問わず、いかなる種類の保証も行われません。
 * 本ライセンス上の権利および制限を定める具体的な文言は、本ライセンスを参照してください。
 *
 * オリジナルコードおよび初期開発者は、N_H (h.10x64@gmail.com) です。
 *
 * N_H によって作成された部分の著作権表示は次のとおりです。
 *
 * Copyright (C)N_H 2012
 *
 * このファイルの内容は、上記に代えて、
 * GNU General License version2 以降 (以下 GPL とする)、
 * GNU Lesser General Public License Version 2.1 以降 (以下 LGPL とする)、
 * の条件に従って使用することも可能です。
 * この場合、このファイルの使用には上記の条項ではなく GPL または LGPL の条項が適用されます。
 * このファイルの他者による使用を GPL または LGPL の条件によってのみ許可し、
 * MPL による使用を許可したくない対象者は、上記の条項を削除することでその意思を示し、
 * 上記条項を GPL または LGPL で義務付けられている告知およびその他の条項に置き換えてください。
 * 対象者が上記の条項を削除しない場合、
 * 受領者は MPL または GPL または LGPL ライセンスのいずれによってもこのファイルを
 * 使用することができます。」
 *
 * -- License --
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License。You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND、either express or implied。See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Initial Developer of the Original Code is
 *   N_H (h.10x64@gmail.com).
 *
 * Portions created by the Initial Developer are Copyright (C)N_H 2012
 * the Initial Developer。All Rights Reserved.
 *
 * Alternatively、the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL")、or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above。If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL、and not to allow others to
 * use your version of this file under the terms of the MPL、indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL。If you do not delete
 * the provisions above、a recipient may use your version of this file under
 * the terms of any one of the MPL、the GPL or the LGPL.
 *
 * */
package com.magiciansforest.audio.soundrenderer.logic;

import com.magiciansforest.audio.soundrenderer.ApplicationBase;
import com.magiciansforest.audio.soundrenderer.event.CharacterChangeEvent;
import com.magiciansforest.audio.soundrenderer.event.CharacterChangeEventListener;
import com.magiciansforest.audio.soundrenderer.logic.mmd.VMD;
import java.io.File;
import java.io.IOException;
import java.util.*;
import projectkyoto.jme3.mmd.PMDNode;

/**
 *
 * @author N_H
 */
public class MMDCharacterManager {

    private SoundEnvironment soundEnvironment;
    private ApplicationBase app;
    private Movie mov;
    private Map<Long, MMDCharacter> characters = new HashMap<Long, MMDCharacter>();
    private Map<PMDNode, Long> nodeIDMap = new HashMap<PMDNode, Long>();
    private int lastFrameNo = 0;
    private boolean isVMDPaused = false;
    private List<CharacterChangeEventListener> listeners = new ArrayList<CharacterChangeEventListener>();

    public MMDCharacterManager(ApplicationBase app, Movie mov, SoundEnvironment env) {
        this.app = app;
        this.mov = mov;
        this.soundEnvironment = env;
    }
    
    public void reset() {
        Long[] ids = characters.keySet().toArray(new Long[]{});
        for (int i = 0; i < ids.length; i++) {
            removeCharacter(ids[i]);
        }
        
        characters.clear();
        nodeIDMap.clear();
        lastFrameNo = 0;
    }
    
    public void adjust(int frameNo) {
        for (MMDCharacter c : characters.values()) {
            if (c.getFrameNo() != frameNo) {
                c.setFrameNo(frameNo);
            }
        }
    }

//    public void update(float tpf) {
//        for (MMDCharacter c : characters.values()) {
//            c.update(tpf);
//            c.getModel().getModel().update();
//        }
//    }

    public long loadPMDFile(File f) {
        if (f.isDirectory()) {
            throw new IllegalArgumentException("f isn't file");
        }

        MMDCharacter ret = new MMDCharacter(app, mov.getTimeKeeper(), IDCreator.getCharactorIDCreator().getNewID(), f);
        characters.put(ret.getID(), ret);
        nodeIDMap.put(ret.getModel().getModel(), ret.getID());

        app.addOperation(new AttachChild(app.getRootNode(), ret.getModel()));

        fireCharacterChageEvent(new CharacterChangeEvent(this, ret.getID(), CharacterChangeEvent.CREATED));

        return ret.getID();
    }

    public VMD loadVMDFile(File f) throws IOException {
        if (f.isDirectory()) {
            throw new IllegalArgumentException("f isn't file");
        }

        return new VMD(f);
    }

    public void setVMDPause(boolean isPaused) {
        for (MMDCharacter c : characters.values()) {
            c.setPause(isPaused);
        }
        isVMDPaused = true;
    }

    public boolean isVMDPaused() {
        return isVMDPaused;
    }

    public void setVMDEnabled(boolean isEnabled) {
        for (MMDCharacter c : characters.values()) {
            c.setEnabled(isEnabled);
        }
    }

    public Iterator<MMDCharacter> getCharacterSet() {
        return characters.values().iterator();
    }

    public MMDCharacter getCharacter(Long id) {
        return characters.get(id);
    }

    public boolean removeCharacter(Long id) {
        if (!characters.containsKey(id)) {
            return false;
        }

        MMDCharacter c = characters.get(id);

        c.removeAllSources();

        app.addOperation(new DetachChild(c.getModel().getParent(), c.getModel()));

        nodeIDMap.remove(c.getModel().getModel());
        characters.remove(id);

        lastFrameNo = 0;
        for (MMDCharacter chr : characters.values()) {
            lastFrameNo = Math.max(lastFrameNo, chr.getLastFrameNo());
        }

        fireCharacterChageEvent(new CharacterChangeEvent(this, c.getID(), CharacterChangeEvent.REMOVED));

        return true;
    }

    public boolean removeCharacter(PMDNode node) {
        if (!nodeIDMap.containsKey(node)) {
            return false;
        }

        return removeCharacter(nodeIDMap.get(node));
    }

    public boolean bind(long characterID, VMD vmd) {
        if (!characters.containsKey(characterID)) {
            return false;
        }
        if (vmd == null) {
            return false;
        }

        MMDCharacter c = characters.get(characterID);
        c.setVMD(vmd);

        lastFrameNo = 0;
        for (MMDCharacter chr : characters.values()) {
            lastFrameNo = Math.max(lastFrameNo, chr.getLastFrameNo());
        }
        
        app.addOperation(new SetFrameNo(c, mov.getMovieFramePosition()));

        fireCharacterChageEvent(new CharacterChangeEvent(this, c.getID(), CharacterChangeEvent.CHANGED));

        return true;
    }

    public boolean bind(long sourceID, long characterID, String boneName) {
        if (!soundEnvironment.containsSource(sourceID)) {
            return false;
        }

        if (!characters.containsKey(characterID)) {
            soundEnvironment.setParentID(sourceID, -1);
            soundEnvironment.setBoneName(sourceID, null);

            return true;
        } else {
            MMDCharacter c = characters.get(characterID);

            c.addSoundSource(boneName, soundEnvironment.getSource(sourceID));
            
            soundEnvironment.setParentID(sourceID, characterID);
            soundEnvironment.setBoneName(sourceID, boneName);

            fireCharacterChageEvent(new CharacterChangeEvent(this, c.getID(), CharacterChangeEvent.CHANGED));

            return true;
        }
    }

    public void setFramePosition(int frameNo) {
        if (frameNo < 0) {
            return;
        }

        for (MMDCharacter c : characters.values()) {
            app.addOperation(new SetFrameNo(c, frameNo));
        }
    }

    public int getLastFrameNo() {
        return lastFrameNo;
    }

    public void addCharacterChangeEventListener(CharacterChangeEventListener listener) {
        listeners.add(listener);
    }

    public void removeCharacterChangeEventListener(CharacterChangeEventListener listener) {
        listeners.remove(listener);
    }

    private void fireCharacterChageEvent(CharacterChangeEvent e) {
        for (CharacterChangeEventListener listener : listeners) {
            listener.characterChange(e);
        }
    }
}
